//     __ _____ _____ _____
//  __|  |   __|     |   | |  JSON for Modern C++
// |  |  |__   |  |  | | | |  version 3.11.3
// |_____|_____|_____|_|___|  https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

#pragma once

#include <cstddef>
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/string_concat.hpp>
#include <string>   // string
#include <utility>  // move
#include <vector>   // vector

NLOHMANN_JSON_NAMESPACE_BEGIN

/*!
@brief SAX interface

This class describes the SAX interface used by @ref nlohmann::json::sax_parse.
Each function is called in different situations while the input is parsed. The
boolean return value informs the parser whether to continue processing the
input.
*/
template <typename BasicJsonType>
struct json_sax {
  using number_integer_t = typename BasicJsonType::number_integer_t;
  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
  using number_float_t = typename BasicJsonType::number_float_t;
  using string_t = typename BasicJsonType::string_t;
  using binary_t = typename BasicJsonType::binary_t;

  /*!
  @brief a null value was read
  @return whether parsing should proceed
  */
  virtual bool null() = 0;

  /*!
  @brief a boolean value was read
  @param[in] val  boolean value
  @return whether parsing should proceed
  */
  virtual bool boolean(bool val) = 0;

  /*!
  @brief an integer number was read
  @param[in] val  integer value
  @return whether parsing should proceed
  */
  virtual bool number_integer(number_integer_t val) = 0;

  /*!
  @brief an unsigned integer number was read
  @param[in] val  unsigned integer value
  @return whether parsing should proceed
  */
  virtual bool number_unsigned(number_unsigned_t val) = 0;

  /*!
  @brief a floating-point number was read
  @param[in] val  floating-point value
  @param[in] s    raw token value
  @return whether parsing should proceed
  */
  virtual bool number_float(number_float_t val, const string_t &s) = 0;

  /*!
  @brief a string value was read
  @param[in] val  string value
  @return whether parsing should proceed
  @note It is safe to move the passed string value.
  */
  virtual bool string(string_t &val) = 0;

  /*!
  @brief a binary value was read
  @param[in] val  binary value
  @return whether parsing should proceed
  @note It is safe to move the passed binary value.
  */
  virtual bool binary(binary_t &val) = 0;

  /*!
  @brief the beginning of an object was read
  @param[in] elements  number of object elements or -1 if unknown
  @return whether parsing should proceed
  @note binary formats may report the number of elements
  */
  virtual bool start_object(std::size_t elements) = 0;

  /*!
  @brief an object key was read
  @param[in] val  object key
  @return whether parsing should proceed
  @note It is safe to move the passed string.
  */
  virtual bool key(string_t &val) = 0;

  /*!
  @brief the end of an object was read
  @return whether parsing should proceed
  */
  virtual bool end_object() = 0;

  /*!
  @brief the beginning of an array was read
  @param[in] elements  number of array elements or -1 if unknown
  @return whether parsing should proceed
  @note binary formats may report the number of elements
  */
  virtual bool start_array(std::size_t elements) = 0;

  /*!
  @brief the end of an array was read
  @return whether parsing should proceed
  */
  virtual bool end_array() = 0;

  /*!
  @brief a parse error occurred
  @param[in] position    the position in the input where the error occurs
  @param[in] last_token  the last read token
  @param[in] ex          an exception object describing the error
  @return whether parsing should proceed (must return false)
  */
  virtual bool parse_error(std::size_t position, const std::string &last_token,
                           const detail::exception &ex) = 0;

  json_sax() = default;
  json_sax(const json_sax &) = default;
  json_sax(json_sax &&) noexcept = default;
  json_sax &operator=(const json_sax &) = default;
  json_sax &operator=(json_sax &&) noexcept = default;
  virtual ~json_sax() = default;
};

namespace detail {
/*!
@brief SAX implementation to create a JSON value from SAX events

This class implements the @ref json_sax interface and processes the SAX events
to create a JSON value which makes it basically a DOM parser. The structure or
hierarchy of the JSON value is managed by the stack `ref_stack` which contains
a pointer to the respective array or object for each recursion depth.

After successful parsing, the value that is passed by reference to the
constructor contains the parsed value.

@tparam BasicJsonType  the JSON type
*/
template <typename BasicJsonType>
class json_sax_dom_parser {
 public:
  using number_integer_t = typename BasicJsonType::number_integer_t;
  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
  using number_float_t = typename BasicJsonType::number_float_t;
  using string_t = typename BasicJsonType::string_t;
  using binary_t = typename BasicJsonType::binary_t;

  /*!
  @param[in,out] r  reference to a JSON value that is manipulated while
                     parsing
  @param[in] allow_exceptions_  whether parse errors yield exceptions
  */
  explicit json_sax_dom_parser(BasicJsonType &r,
                               const bool allow_exceptions_ = true)
      : root(r), allow_exceptions(allow_exceptions_) {}

  // make class move-only
  json_sax_dom_parser(const json_sax_dom_parser &) = delete;
  json_sax_dom_parser(json_sax_dom_parser &&) =
      default;  // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)
  json_sax_dom_parser &operator=(const json_sax_dom_parser &) = delete;
  json_sax_dom_parser &operator=(json_sax_dom_parser &&) =
      default;  // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)
  ~json_sax_dom_parser() = default;

  bool null() {
    handle_value(nullptr);
    return true;
  }

  bool boolean(bool val) {
    handle_value(val);
    return true;
  }

  bool number_integer(number_integer_t val) {
    handle_value(val);
    return true;
  }

  bool number_unsigned(number_unsigned_t val) {
    handle_value(val);
    return true;
  }

  bool number_float(number_float_t val, const string_t & /*unused*/) {
    handle_value(val);
    return true;
  }

  bool string(string_t &val) {
    handle_value(val);
    return true;
  }

  bool binary(binary_t &val) {
    handle_value(std::move(val));
    return true;
  }

  bool start_object(std::size_t len) {
    ref_stack.push_back(handle_value(BasicJsonType::value_t::object));

    if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) &&
                             len > ref_stack.back()->max_size())) {
      JSON_THROW(out_of_range::create(
          408, concat("excessive object size: ", std::to_string(len)),
          ref_stack.back()));
    }

    return true;
  }

  bool key(string_t &val) {
    JSON_ASSERT(!ref_stack.empty());
    JSON_ASSERT(ref_stack.back()->is_object());

    // add null at given key and store the reference for later
    object_element =
        &(ref_stack.back()->m_data.m_value.object->operator[](val));
    return true;
  }

  bool end_object() {
    JSON_ASSERT(!ref_stack.empty());
    JSON_ASSERT(ref_stack.back()->is_object());

    ref_stack.back()->set_parents();
    ref_stack.pop_back();
    return true;
  }

  bool start_array(std::size_t len) {
    ref_stack.push_back(handle_value(BasicJsonType::value_t::array));

    if (JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) &&
                             len > ref_stack.back()->max_size())) {
      JSON_THROW(out_of_range::create(
          408, concat("excessive array size: ", std::to_string(len)),
          ref_stack.back()));
    }

    return true;
  }

  bool end_array() {
    JSON_ASSERT(!ref_stack.empty());
    JSON_ASSERT(ref_stack.back()->is_array());

    ref_stack.back()->set_parents();
    ref_stack.pop_back();
    return true;
  }

  template <class Exception>
  bool parse_error(std::size_t /*unused*/, const std::string & /*unused*/,
                   const Exception &ex) {
    errored = true;
    static_cast<void>(ex);
    if (allow_exceptions) {
      JSON_THROW(ex);
    }
    return false;
  }

  constexpr bool is_errored() const { return errored; }

 private:
  /*!
  @invariant If the ref stack is empty, then the passed value will be the new
             root.
  @invariant If the ref stack contains a value, then it is an array or an
             object to which we can add elements
  */
  template <typename Value>
  JSON_HEDLEY_RETURNS_NON_NULL BasicJsonType *handle_value(Value &&v) {
    if (ref_stack.empty()) {
      root = BasicJsonType(std::forward<Value>(v));
      return &root;
    }

    JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object());

    if (ref_stack.back()->is_array()) {
      ref_stack.back()->m_data.m_value.array->emplace_back(
          std::forward<Value>(v));
      return &(ref_stack.back()->m_data.m_value.array->back());
    }

    JSON_ASSERT(ref_stack.back()->is_object());
    JSON_ASSERT(object_element);
    *object_element = BasicJsonType(std::forward<Value>(v));
    return object_element;
  }

  /// the parsed JSON value
  BasicJsonType &root;
  /// stack to model hierarchy of values
  std::vector<BasicJsonType *> ref_stack{};
  /// helper to hold the reference for the next object element
  BasicJsonType *object_element = nullptr;
  /// whether a syntax error occurred
  bool errored = false;
  /// whether to throw exceptions in case of errors
  const bool allow_exceptions = true;
};

template <typename BasicJsonType>
class json_sax_dom_callback_parser {
 public:
  using number_integer_t = typename BasicJsonType::number_integer_t;
  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
  using number_float_t = typename BasicJsonType::number_float_t;
  using string_t = typename BasicJsonType::string_t;
  using binary_t = typename BasicJsonType::binary_t;
  using parser_callback_t = typename BasicJsonType::parser_callback_t;
  using parse_event_t = typename BasicJsonType::parse_event_t;

  json_sax_dom_callback_parser(BasicJsonType &r, const parser_callback_t cb,
                               const bool allow_exceptions_ = true)
      : root(r), callback(cb), allow_exceptions(allow_exceptions_) {
    keep_stack.push_back(true);
  }

  // make class move-only
  json_sax_dom_callback_parser(const json_sax_dom_callback_parser &) = delete;
  json_sax_dom_callback_parser(json_sax_dom_callback_parser &&) =
      default;  // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)
  json_sax_dom_callback_parser &operator=(
      const json_sax_dom_callback_parser &) = delete;
  json_sax_dom_callback_parser &operator=(json_sax_dom_callback_parser &&) =
      default;  // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)
  ~json_sax_dom_callback_parser() = default;

  bool null() {
    handle_value(nullptr);
    return true;
  }

  bool boolean(bool val) {
    handle_value(val);
    return true;
  }

  bool number_integer(number_integer_t val) {
    handle_value(val);
    return true;
  }

  bool number_unsigned(number_unsigned_t val) {
    handle_value(val);
    return true;
  }

  bool number_float(number_float_t val, const string_t & /*unused*/) {
    handle_value(val);
    return true;
  }

  bool string(string_t &val) {
    handle_value(val);
    return true;
  }

  bool binary(binary_t &val) {
    handle_value(std::move(val));
    return true;
  }

  bool start_object(std::size_t len) {
    // check callback for object start
    const bool keep = callback(static_cast<int>(ref_stack.size()),
                               parse_event_t::object_start, discarded);
    keep_stack.push_back(keep);

    auto val = handle_value(BasicJsonType::value_t::object, true);
    ref_stack.push_back(val.second);

    // check object limit
    if (ref_stack.back() &&
        JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) &&
                             len > ref_stack.back()->max_size())) {
      JSON_THROW(out_of_range::create(
          408, concat("excessive object size: ", std::to_string(len)),
          ref_stack.back()));
    }

    return true;
  }

  bool key(string_t &val) {
    BasicJsonType k = BasicJsonType(val);

    // check callback for key
    const bool keep =
        callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);
    key_keep_stack.push_back(keep);

    // add discarded value at given key and store the reference for later
    if (keep && ref_stack.back()) {
      object_element = &(
          ref_stack.back()->m_data.m_value.object->operator[](val) = discarded);
    }

    return true;
  }

  bool end_object() {
    if (ref_stack.back()) {
      if (!callback(static_cast<int>(ref_stack.size()) - 1,
                    parse_event_t::object_end, *ref_stack.back())) {
        // discard object
        *ref_stack.back() = discarded;
      } else {
        ref_stack.back()->set_parents();
      }
    }

    JSON_ASSERT(!ref_stack.empty());
    JSON_ASSERT(!keep_stack.empty());
    ref_stack.pop_back();
    keep_stack.pop_back();

    if (!ref_stack.empty() && ref_stack.back() &&
        ref_stack.back()->is_structured()) {
      // remove discarded value
      for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end();
           ++it) {
        if (it->is_discarded()) {
          ref_stack.back()->erase(it);
          break;
        }
      }
    }

    return true;
  }

  bool start_array(std::size_t len) {
    const bool keep = callback(static_cast<int>(ref_stack.size()),
                               parse_event_t::array_start, discarded);
    keep_stack.push_back(keep);

    auto val = handle_value(BasicJsonType::value_t::array, true);
    ref_stack.push_back(val.second);

    // check array limit
    if (ref_stack.back() &&
        JSON_HEDLEY_UNLIKELY(len != static_cast<std::size_t>(-1) &&
                             len > ref_stack.back()->max_size())) {
      JSON_THROW(out_of_range::create(
          408, concat("excessive array size: ", std::to_string(len)),
          ref_stack.back()));
    }

    return true;
  }

  bool end_array() {
    bool keep = true;

    if (ref_stack.back()) {
      keep = callback(static_cast<int>(ref_stack.size()) - 1,
                      parse_event_t::array_end, *ref_stack.back());
      if (keep) {
        ref_stack.back()->set_parents();
      } else {
        // discard array
        *ref_stack.back() = discarded;
      }
    }

    JSON_ASSERT(!ref_stack.empty());
    JSON_ASSERT(!keep_stack.empty());
    ref_stack.pop_back();
    keep_stack.pop_back();

    // remove discarded value
    if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) {
      ref_stack.back()->m_data.m_value.array->pop_back();
    }

    return true;
  }

  template <class Exception>
  bool parse_error(std::size_t /*unused*/, const std::string & /*unused*/,
                   const Exception &ex) {
    errored = true;
    static_cast<void>(ex);
    if (allow_exceptions) {
      JSON_THROW(ex);
    }
    return false;
  }

  constexpr bool is_errored() const { return errored; }

 private:
  /*!
  @param[in] v  value to add to the JSON value we build during parsing
  @param[in] skip_callback  whether we should skip calling the callback
             function; this is required after start_array() and
             start_object() SAX events, because otherwise we would call the
             callback function with an empty array or object, respectively.

  @invariant If the ref stack is empty, then the passed value will be the new
             root.
  @invariant If the ref stack contains a value, then it is an array or an
             object to which we can add elements

  @return pair of boolean (whether value should be kept) and pointer (to the
          passed value in the ref_stack hierarchy; nullptr if not kept)
  */
  template <typename Value>
  std::pair<bool, BasicJsonType *> handle_value(
      Value &&v, const bool skip_callback = false) {
    JSON_ASSERT(!keep_stack.empty());

    // do not handle this value if we know it would be added to a discarded
    // container
    if (!keep_stack.back()) {
      return {false, nullptr};
    }

    // create value
    auto value = BasicJsonType(std::forward<Value>(v));

    // check callback
    const bool keep =
        skip_callback || callback(static_cast<int>(ref_stack.size()),
                                  parse_event_t::value, value);

    // do not handle this value if we just learnt it shall be discarded
    if (!keep) {
      return {false, nullptr};
    }

    if (ref_stack.empty()) {
      root = std::move(value);
      return {true, &root};
    }

    // skip this value if we already decided to skip the parent
    // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360)
    if (!ref_stack.back()) {
      return {false, nullptr};
    }

    // we now only expect arrays and objects
    JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object());

    // array
    if (ref_stack.back()->is_array()) {
      ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value));
      return {true, &(ref_stack.back()->m_data.m_value.array->back())};
    }

    // object
    JSON_ASSERT(ref_stack.back()->is_object());
    // check if we should store an element for the current key
    JSON_ASSERT(!key_keep_stack.empty());
    const bool store_element = key_keep_stack.back();
    key_keep_stack.pop_back();

    if (!store_element) {
      return {false, nullptr};
    }

    JSON_ASSERT(object_element);
    *object_element = std::move(value);
    return {true, object_element};
  }

  /// the parsed JSON value
  BasicJsonType &root;
  /// stack to model hierarchy of values
  std::vector<BasicJsonType *> ref_stack{};
  /// stack to manage which values to keep
  std::vector<bool> keep_stack{};
  /// stack to manage which object keys to keep
  std::vector<bool> key_keep_stack{};
  /// helper to hold the reference for the next object element
  BasicJsonType *object_element = nullptr;
  /// whether a syntax error occurred
  bool errored = false;
  /// callback function
  const parser_callback_t callback = nullptr;
  /// whether to throw exceptions in case of errors
  const bool allow_exceptions = true;
  /// a discarded value for the callback
  BasicJsonType discarded = BasicJsonType::value_t::discarded;
};

template <typename BasicJsonType>
class json_sax_acceptor {
 public:
  using number_integer_t = typename BasicJsonType::number_integer_t;
  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
  using number_float_t = typename BasicJsonType::number_float_t;
  using string_t = typename BasicJsonType::string_t;
  using binary_t = typename BasicJsonType::binary_t;

  bool null() { return true; }

  bool boolean(bool /*unused*/) { return true; }

  bool number_integer(number_integer_t /*unused*/) { return true; }

  bool number_unsigned(number_unsigned_t /*unused*/) { return true; }

  bool number_float(number_float_t /*unused*/, const string_t & /*unused*/) {
    return true;
  }

  bool string(string_t & /*unused*/) { return true; }

  bool binary(binary_t & /*unused*/) { return true; }

  bool start_object(std::size_t /*unused*/ = static_cast<std::size_t>(-1)) {
    return true;
  }

  bool key(string_t & /*unused*/) { return true; }

  bool end_object() { return true; }

  bool start_array(std::size_t /*unused*/ = static_cast<std::size_t>(-1)) {
    return true;
  }

  bool end_array() { return true; }

  bool parse_error(std::size_t /*unused*/, const std::string & /*unused*/,
                   const detail::exception & /*unused*/) {
    return false;
  }
};

}  // namespace detail
NLOHMANN_JSON_NAMESPACE_END
